/* Copyright (C) 2010 Fredrique Samuels, fredriquesamuels@gmail.com */

/*
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "graphics.h"
#include "base/rectangle.h"

#include <math.h>
#include <iostream>

//
//std::ostream &operator<<( std::ostream& out, const tri::graphics::color& c){
//	out<<"<Color: ";
//	out<<" r="<<c.getRed();
//	out<<" g="<<c.getGreen();
//	out<<" b="<<c.getBlue();
//	out<<" a="<<c.getAlpha();
//	out<<" >"<<std::endl;
//	return out;
//}

namespace tri {

graphics::RGBA::RGBA(int r, int g, int b, int a):red(r),green(g),blue(b),alpha(a){
}

void graphics::RGBA::set(RGBA& color){
	red = color.red;
	green = color.green;
	blue = color.blue;
	alpha = color.alpha;
}


graphics::TColor::TColor() {
	setValue(255, 255, 255, 255);
}
graphics::TColor::TColor(int r, int g, int b, int a) {
	setValue(r, g, b, a);
}

graphics::TColor::TColor(graphics::RGBA c) {
	setValue(c.red, c.green, c.blue, c.alpha);
}


graphics::TColor::TColor(float c[4]){
	setValue(c);
}

graphics::TColor::~TColor() {
}


graphics::RGBA graphics::TColor::asRGBA(){
	return RGBA((int)(r.getValue()),(int)(g.getValue()),(int)(b.getValue()),(int)(a.getValue()));
}

void graphics::TColor::setRed(int r) {
	this->r.setValue( ((float)r));
}
void graphics::TColor::setGreen(int g) {
	this->g.setValue( ((float)g));
}
void graphics::TColor::setBlue(int b) {
	this->b.setValue( ((float)b));
}
void graphics::TColor::setAlpha(int a) {
	this->a.setValue( ((float)a));
}

void graphics::TColor::setTargetRed(int r, float t) {
	this->r.setTargetValue( (float)r, t);
}
void graphics::TColor::setTargetGreen(int g, float t) {
	this->g.setTargetValue( ((float)g) , t);
}
void graphics::TColor::setTargetBlue(int b, float t) {
	this->b.setTargetValue( ((float)b), t);
}
void graphics::TColor::setTargetAlpha(int a, float t) {
	this->a.setTargetValue( ((float)a) , t);
}

void graphics::TColor::setTargetValue(int r, int g, int b, int a, float t) {
	setTargetRed(r, t);
	setTargetGreen(g, t);
	setTargetBlue(b, t);
	setTargetAlpha(a, t);
}

int graphics::TColor::getRed() {
	return (int)((float)r.getValue());
}
int graphics::TColor::getGreen() {
	return (int)((float)g.getValue());
}
int graphics::TColor::getBlue() {
	return (int)((float)b.getValue());
}
int graphics::TColor::getAlpha() {
	return (int)((float)a.getValue());
}

void graphics::TColor::setValue(int r, int g, int b, int a) {
	this->r.setValue(r);
	this->b.setValue(b);
	this->g.setValue(g);
	this->a.setValue(a);
}

void graphics::TColor::setValue(float c[4]) {
	this->r.setValue(c[0]);
	this->b.setValue(c[2]);
	this->g.setValue(c[1]);
	this->a.setValue(c[3]);
}

void graphics::TColor::setValue(RGBA rgba){
	setRed(rgba.red);
	setBlue(rgba.blue);
	setGreen(rgba.green);
	setAlpha(rgba.alpha);
}

void graphics::TColor::get_gl_values(float *c) {
	c[0] = r.getValue()/255.0;
	c[1] = g.getValue()/255.0;
	c[2] = b.getValue()/255.0;
	c[3] = a.getValue()/255.0;
}
void graphics::TColor::apply() {
	float c[4];
	get_gl_values(c);
	glColor4fv(c);
}
void graphics::TColor::mult(float v) {
	r.mult(v);
	g.mult(v);
	b.mult(v);
	a.mult(v);
}

void graphics::TColor::print() {
	std::cout<<"<Color: ";
	std::cout<<" r="<<getRed();
	std::cout<<" g="<<getGreen();
	std::cout<<" b="<<getBlue();
	std::cout<<" a="<<getAlpha();
	std::cout<<" >"<<std::endl;
}

graphics::TColor graphics::TColor::copy() {
	float c[4];
	get_gl_values(c);
	return graphics::TColor(c);
}

void graphics::renderGlyph(float w, float h, TColor * c, TColor *c_bottom,
		TColor *c_top_right, TColor *c_bottom_right, tri::TRectangle* tc,
		float _x, float _y, float _z, float clip_l, float clip_t, float clip_r,
		float clip_b) {

	if (clip_l >= w || clip_r >= w|| clip_l+clip_r>=w) {
		return;
	}

	float x = 0.5*w;
	float y = 0.5*h;

	float verts[] = { -x+clip_l, y-clip_t, 0, x-clip_r, y-clip_t, 0, -x+clip_l,
			-y+clip_b, 0, x-clip_r, -y+clip_b, 0 };

	float n = 0.577;
	float normals[] = { -n, -n, n, n, -n, n, n, n, n, -n, n, n };

	tri::TRectangle texc(0, 0, 1, 1);
	if (tc==NULL) {
		tc = &texc;
	}

	float v_diff = tc->value[2]-tc->value[0];
	tc->value[0] += v_diff * clip_l/w;
	tc->value[2] -= v_diff * clip_r/w;

	float h_diff = tc->value[3]-tc->value[1];
	tc->value[1] += h_diff * clip_t/h;
	tc->value[3] -= h_diff * clip_b/h;

	float tex[8];
	tc->getRectValue(tex);

	GLbyte indices[] = {2,3,1,0 };

	float col[16];
	TColor white;
	white.get_gl_values(col);
	white.get_gl_values(&col[4]);
	white.get_gl_values(&col[8]);
	white.get_gl_values(&col[12]);
	if (c!=NULL) {
		c->get_gl_values(col);
		c->get_gl_values(&col[4]);
		c->get_gl_values(&col[8]);
		c->get_gl_values(&col[12]);
	}

	if (c_bottom != NULL) {
		c_bottom->get_gl_values(col);
		c_bottom->get_gl_values(&col[4]);
	}

	if (c_top_right != NULL) {
		c_top_right->get_gl_values(&col[8]);
	}

	if (c_bottom_right != NULL) {
		c_bottom_right->get_gl_values(&col[4]);
	}

	//		glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
	glPushMatrix();
	glTranslated(_x, _y, _z);
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, verts);
	glNormalPointer(GL_FLOAT,0,normals);
	glColorPointer(4, GL_FLOAT, 0, col);
	glTexCoordPointer(2, GL_FLOAT, 0, tex);
	glDrawElements(GL_QUADS,4,GL_UNSIGNED_BYTE,indices);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glPopMatrix();

}

void graphics::initGraphics() {
	glShadeModel(GL_SMOOTH); //#Enables Smooth Color Shading
	glClearColor(0.0, 0.0, 0.0, 0.0) ; //  #This Will Clear The Background Color To Black
	glClearDepth(1.0); //#Enables Clearing Of The Depth Buffer
	glClearStencil(0);
	glEnable(GL_DEPTH_TEST); // #Enables Depth Testing
	glDepthFunc(GL_LEQUAL); //#The Type Of Depth Test To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //# Really Nice Perspective Calculations
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	//    glEnable(GL_LIGHTING);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER,0.1);
	glEnable(GL_COLOR_MATERIAL);
//	glColorMaterial ( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ) ;
	//    glClearAccum(0.0, 0.0, 0.0, 0.0);
	//    glEnable(GL_NORMALIZE);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	initCellShadingTexture();

}

void graphics::setBackgroundColor(TColor* c) {
	float fc[4];
	c->get_gl_values(fc);
	GLSETBG(fc[0],fc[1],fc[2],fc[3]);
}

void graphics::setBackgroundColor(int r, int g, int b, int a){
	TColor c(r,g,b,a);
	setBackgroundColor(&c);
}

void graphics::translate(tri::TAniVector &vec) {
	glTranslated(vec.getX(), vec.getY(), vec.getZ());
}

void graphics::translate(tri::TVector vec) {
	glTranslated(vec.x, vec.y, vec.z);
}

void graphics::rotate(tri::TAniVector &v) {
	glRotated(v.getX(), 1, 0, 0);
	glRotated(v.getY(), 0, 1, 0);
	glRotated(v.getZ(), 0, 0, 1);
}

void graphics::rotate(tri::TVector v) {
	glRotated(v.x, 1, 0, 0);
	glRotated(v.y, 0, 1, 0);
	glRotated(v.z, 0, 0, 1);
}

void graphics::loadname(int id) {
	glLoadName(id);
}
void graphics::pushMatrix() {
	glPushMatrix();
}
void graphics::popMatrix() {
	glPopMatrix();
}

void graphics::reduceVectorToUnitLenght(TVector *TVector) // Reduces A Normal Vector (3 Coordinates)
{ // To A Unit Normal Vector With A Length Of One.
	float length; // Holds Unit Length
	// Calculates The Length Of The Vector
	length = (float)sqrt((TVector->x*TVector->x) + (TVector->y*TVector->y)
			+ (TVector->z*TVector->z));
	if (length == 0.0f) // Prevents Divide By 0 Error By Providing
		length = 1.0f; // An Acceptable Value For Vectors To Close To 0.
	TVector->x /= length; // Dividing Each Element By
	TVector->y /= length; // The Length Results In A
	TVector->z /= length; // Unit Normal Vector.
}

void graphics::calcNormalVector(const TVector *_v0, const TVector *_v1,
		const TVector *_v2, TVector *out) {
	float v1[3], v2[3]; // Vector 1 (x,y,z) & Vector 2 (x,y,z)
	int x = 0;
	int y = 2;
	int z = 3;

	// Finds The Vector Between 2 Points By Subtracting
	// The x,y,z Coordinates From One Point To Another.
	// Calculate The Vector From Point 1 To Point 0
	v1[x] = _v0->x - _v1->x; // Vector 1.x=Vertex[0].x-Vertex[1].x
	v1[y] = _v0->y - _v1->y; // Vector 1.y=Vertex[0].y-Vertex[1].y
	v1[z] = _v0->z - _v1->z; // Vector 1.z=Vertex[0].y-Vertex[1].z
	// Calculate The Vector From Point 2 To Point 1
	v2[x] = _v1->x - _v2->x; // Vector 2.x=Vertex[0].x-Vertex[1].x
	v2[y] = _v1->y - _v2->y; // Vector 2.y=Vertex[0].y-Vertex[1].y
	v2[z] = _v1->z - _v2->z; // Vector 2.z=Vertex[0].z-Vertex[1].z
	// Compute The Cross Product To Give Us A Surface Normal
	out->x = v1[y]*v2[z]- v1[z]*v2[y]; // Cross Product For Y - Z
	out->y = v1[z]*v2[x]- v1[x]*v2[z]; // Cross Product For X - Z
	out->z = v1[x]*v2[y]- v1[y]*v2[x]; // Cross Product For X - Y
	reduceVectorToUnitLenght(out); // Normalize The Vectors

}

unsigned int graphics::CELL_SHADING_TEXTURE_ID = 0;

void graphics::initCellShadingTexture(){
	if(graphics::CELL_SHADING_TEXTURE_ID != 0){
		glEnable(GL_TEXTURE_1D);
		
		glGenTextures (1, &graphics::CELL_SHADING_TEXTURE_ID);						// Get A Free Texture ID ( NEW )

		glBindTexture (GL_TEXTURE_1D, graphics::CELL_SHADING_TEXTURE_ID);			// Bind This Texture. From Now On It Will Be 1D ( NEW )

		// For Crying Out Loud Don't Let OpenGL Use Bi/Trilinear Filtering! ( NEW )
		glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);	
		glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		float  shader_data[] = {0.2,0.2,0.2,0.2,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
		glTexImage1D (GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shader_data);
	}
}

void graphics::bindShader(){
	glBindTexture (GL_TEXTURE_1D, graphics::CELL_SHADING_TEXTURE_ID);
}

}
